package com.xtpt.disaster.common;

import com.alibaba.fastjson.JSON;
import com.xtpt.disaster.bean.DbnodeInfo;
import com.xtpt.disaster.common.consts.BackupTypeEnum;
import com.xtpt.disaster.common.consts.OSTypeEenum;
import com.xtpt.disaster.common.consts.TaskTypeEnum;
import com.xtpt.disaster.common.consts.TransFileEnum;
import com.xtpt.disaster.utils.LogUtil;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component
public abstract class ClientCmdBuilder {
    /**
     * @Description: 加载服务端配置
     * @Author: hpc
     * @Date: 2021/12/31 下午 2:55
     **/
    @Autowired
    PropertyConfig config;
    /**
     * 远端命令回调地址（不填写则不回调）
     */
    private String callBackUrl;
    /**
     * 当前操作命令组
     */
    private List<String> currentGroup;
    /**
     * 待执行命令列表
     */
    private List<List<String>> cmdGroupList;
    /**
     * 客户端换行符(默认为Linux)
     */
    private String osLineSeparator="\n";

    /**
     * @Description: 操作系统类型
     * @Author: hpc
     * @Date: 2021/12/31 下午 2:21
     **/
    private OSTypeEenum osType;

    /**
     * 构造函数,构造后需设置客户端类型,否则默认Linux类型
     * @throws Exception 系统类型错误异常
     */
    public ClientCmdBuilder () {
        this.init();
    }
    /**
     * 初始化
     */
    public ClientCmdBuilder init()
    {
        currentGroup =new ArrayList<>();
        cmdGroupList = new ArrayList<>();
        callBackUrl="";
        osLineSeparator="\n";
        this.osType=OSTypeEenum.LINUX;
        return this;
    }

    public ClientCmdBuilder setOSType(OSTypeEenum osType) throws Exception
    {
        this.osType = osType;
        if(osType == OSTypeEenum.LINUX)
        {
            osLineSeparator="\n";
        }
        else if(osType == OSTypeEenum.WINDOWS)
        {
            osLineSeparator="\r\n";
        }
        else
        {
            throw new Exception("系统类型不支持！");
        }
        return this;
    }

    /**
     * 如果需要结果上报则设置回调接口
     * @param callBackUrl 回调接口
     * @return this
     */
    public ClientCmdBuilder setResultCallBackUrl(String callBackUrl)
    {
        this.callBackUrl=callBackUrl;
        return this;
    }

    /**
     * 对当前组添加命令（不自动加回车符）
     * @param cmd 命令字符串
     * @return this
     */
    public ClientCmdBuilder AddCmd(String cmd)
    {
        if(cmd!=null && !cmd.isEmpty())
        {
            currentGroup.add(cmd);
        }
        return this;
    }
    /**
     * 对当前组添加命令（自动添加回车命令）
     * @param cmd 命令字符串
     * @return this
     */
    public ClientCmdBuilder AddCmdLn(String cmd)
    {
        return AddCmd(cmd).AddCmd(osLineSeparator);
    }

    private ClientCmdBuilder NewGroupOrForBuild()
    {
        if(currentGroup!=null && currentGroup.size()>0)
        {
            cmdGroupList.add(currentGroup);
            currentGroup=new ArrayList<>();
        }
        return this;
    }
    /**
     * 新添加命令组并将命令加入到新组（不自动加回车符）
     * @param cmd 命令字符串
     * @return this
     */
    public ClientCmdBuilder AddCmdWithNewGroup(String cmd)
    {
        return NewGroupOrForBuild().AddCmd(cmd);
    }
    /**
     * 新添加命令组并将命令加入到新组（自动加回车符）
     * @param cmd 命令字符串
     * @return this
     */
    public ClientCmdBuilder AddCmdWithNewGroupLn(String cmd)
    {
        return NewGroupOrForBuild().AddCmdLn(cmd);
    }

    public CommandBean build()
    {
        NewGroupOrForBuild();
        return new CommandBean(this.callBackUrl,this.cmdGroupList);
    }

    /**
     * 将命令组生成请求json字符串
     * @return string
     */
    public String buildToJSONString()
    {
        return JSON.toJSONString(this.build());
    }

    public String fileName(String filename)
    {
        String separator="/";
        if(filename.lastIndexOf(":\\")==-1 && filename.startsWith("/"))
        {
            separator="/";
        }
        else if(filename.lastIndexOf(":\\")>-1){
            separator = "\\";
        }
        int i = filename.lastIndexOf(separator);
        return i >= 0 ? filename.substring(i + 1) : filename;
    }

    public static String combinePath(String rootPath,String fileName)
    {
        if(StringUtils.isEmpty(fileName))
        {
            return rootPath;
        }
        String separator="/";
        if(rootPath.lastIndexOf(":\\")==-1 && rootPath.startsWith("/"))
        {
            separator="/";
        }
        else if(rootPath.lastIndexOf(":\\")>-1){
            separator = "\\";
        }
        if(rootPath.endsWith(separator))
        {
            return rootPath+fileName;
        }
        else
        {
            return String.format("%s%s%s",rootPath,separator,fileName);
        }
    }
    /**
     * @Description: 拼接文件传输模板
     * @Author: hpc
     * @Date: 2022/1/5 上午 9:29
     * @param transFileTo: 指明文件传输方向
     * @return: 模板
     **/
    private String getCommonTransTemplate(TransFileEnum transFileTo)
    {
        String serverPort=config.getServerSshPort();
        if(StringUtils.isEmpty(serverPort))
        {
            // scp 端口
            serverPort="22";
        }
        // 账户信息
        String accountInfo = String.format(" %s@%s:", config.getServerAccount(),config.getServerIp());
        // ssh 端口信息
        String portInfo= String.format(" -P %s ", serverPort);

        // scp 文件传输使用的工具
        String useTool;
        // 文件夹路径统一加 引号
        String quotFile = "\"%s\"";
        if(osType==OSTypeEenum.LINUX) {
            // linux下使用修改后的pscp免密码输入确认
            useTool = String.format("pscpunix -unsafe -auto-store-key -q -pw %s -r", config.getServerPassword());
        }
        else
        {
            // Windows下通过pscp进行免密码输入确认
            useTool = String.format("pscp.exe -unsafe -auto-store-key -utf8 -q -pw %s -r", config.getServerPassword());
        }
        String trans = useTool + portInfo;
        // 拼接需要用户名密码才能访问的文件访问字符串 （需要密码访问的文件路径统一加引号）
        String tmpString = accountInfo+"\"%s\"";
        // 通过文件夹和文件名拼接文件路径进行文件传输
        if(transFileTo== TransFileEnum.TO_CLIENT)
        {
            trans +=  tmpString +" "+ quotFile;
        }
        if(transFileTo== TransFileEnum.TO_SERVER)
        {
            trans += quotFile + tmpString;
        }

        return trans;
    }
    /**
     * @Description: 生成实际文件传输命令
     * @Author: hpc
     * @Date: 2022/1/5 上午 9:34
     * @param fromSource:
     * @param toTarget:
     * @param transFileTo:
     * @return: java.lang.String
     **/
    private String getTransactionFileCMD(String fromSource, String toTarget, TransFileEnum transFileTo)
    {
        return fixFilePathBank(fromSource,toTarget,transFileTo,getCommonTransTemplate(transFileTo));

    }
    /**
     * 带文件备份类型参数的将客户端文件传递到服务端（仅用于文件的全量增量备份）
     * @param fromSource
     * @param toTarget
     * @param transFileTo
     * @return
     */
    private String getRsyncTransactionFileCMD(String fromSource, String toTarget, TransFileEnum transFileTo)
    {
        String serverPort=config.getServerSshPort();
        if(StringUtils.isEmpty(serverPort))
        {
            // scp 端口
            serverPort="22";
        }
        // 账户信息
        String accountInfo = String.format(" %s@%s:", config.getServerAccount(),config.getServerIp());

        // scp 文件传输使用的工具
        String trans;
        // 文件夹路径统一加 引号
        String quotFile = "\"%s\"";
        if(osType==OSTypeEenum.LINUX) {
            // linux下使用sshpass免密码输入确认
            trans = String.format("sshpass -p %s rsync -avh --delete-after -e 'ssh -p %s' ",
                    config.getServerPassword(),serverPort);
        }
        else
        {
            // Windows下不支持
            LogUtil.logger.error("暂时不支持Windows下的增量文件/文件夹备份！");
            return null;
        }
        // 拼接需要用户名密码才能访问的文件访问字符串 （需要密码访问的文件路径统一加引号）
        String tmpString = accountInfo+"\"%s\"";
        // 通过文件夹和文件名拼接文件路径进行文件传输
        if(transFileTo== TransFileEnum.TO_SERVER)
        {
            trans += quotFile + tmpString;
        }
        else
        {
            // Windows下不支持
            LogUtil.logger.error("暂时不支持增量文件/文件夹恢复！");
            return null;
        }
        // 因为暂时不支持windows 所以不需要 fixFilePathBank
        return String.format(trans, fromSource,toTarget);
    }

    /**
     * @Description: 解决文件/文件夹路径中存在空格的问题（客户端为windows且恢复时需要）
     * @Author: hpc
     * @Date: 2022/4/21 上午 17:38
     * @param fromSource
     * @param toTarget
     * @param transFileTo
     * @param transTemplateString
     * @return
     */
    private String fixFilePathBank(String fromSource, String toTarget, TransFileEnum transFileTo, String transTemplateString)
    {
        String from;
        if(transFileTo == TransFileEnum.TO_CLIENT && osType==OSTypeEenum.WINDOWS)
        {
            from = fromSource.replace(" ",config.getBlankSpaceReplace());
        }
        else
        {
            from = fromSource;
        }
        return String.format(transTemplateString, from,toTarget);
    }

    /**
     * @Description: 服务端向客户端传输文件命令生成
     * @Author: hpc
     * @Date: 2021/12/31 下午 3:18
     * @param fileFrom: 位于服务端的文件绝对路径列表
     * @param fileTo: 客户端目标文件绝对路径
     **/
    public void TransactionFileToClient(String fileFrom,String fileTo)
    {
        AddCmdWithNewGroupLn(getTransactionFileCMD(fileFrom,fileTo, TransFileEnum.TO_CLIENT));
    }

    /**
     * @Description: 客户端向服务端传输多个文件命令生成
     * @Author: hpc
     * @Date: 2022/1/5 上午 9:44
     * @param fileFrom: 位于客户端的文件绝对路径列表
     * @param fileTo: 服务端目标文件绝对路径
     * @return: void
     **/
    public void TransactionFileToServer(String fileFrom,String fileTo)
    {
        AddCmdWithNewGroup(getTransactionFileCMD(fileFrom,fileTo, TransFileEnum.TO_SERVER));
    }

    public void TransactionFilesToClient(List<String> filesList,String clientTargetFolder)
    {
        for (String file :filesList)
        {
            String toFile=combinePath(clientTargetFolder,fileName(file));
            AddCmdWithNewGroup(getTransactionFileCMD(file,toFile, TransFileEnum.TO_CLIENT));
        }
    }

    /**
     * 将客户端文件传递到服务端
     */
    public void TransactionFilesToServer(String clientFilePath, String serverFilePath)
    {
        TransactionFilesToServer(clientFilePath,serverFilePath,BackupTypeEnum.FULL);
    }

    /**
     * !!!带文件备份类型参数的备份方法（仅用于文件的全量增量备份）
     * @param clientFilePath 客户端文件/文件夹路径
     * @param serverFilePath 服务端文件/文件夹路径
     * @param taskBackupType 全量备份=0,增量备份=1
     */
    public void TransactionFilesToServer(String clientFilePath, String serverFilePath, BackupTypeEnum taskBackupType)
    {
        if(taskBackupType == BackupTypeEnum.INCREMENT)
        {
            AddCmdWithNewGroupLn(getRsyncTransactionFileCMD(clientFilePath,serverFilePath,TransFileEnum.TO_SERVER));
        }
        else
        {
            AddCmdWithNewGroupLn(getTransactionFileCMD(clientFilePath,serverFilePath,TransFileEnum.TO_SERVER));
        }
    }

    /**
     * @Description: 获取统一配置的备份文件后缀
     * @Author: hpc
     * @Date: 2021/12/31 下午 3:27
     * @return: java.lang.String
     **/
    public String getBackFileExtend()
    {
        return config.getBackFileExtend();
    }
    /**
     * @Description: 用于临时替换命令中非预期的空格
     * @Author: hpc
     * @Date: 2021/12/31 下午 3:41
     **/
    public String getBlankSpaceReplace()
    {
        return config.getBlankSpaceReplace();
    }

    public abstract ClientCmdBuilder initializeWithClientNode(DbnodeInfo nodeInfo) throws Exception;
    public abstract Map<String,String> dbBackUp(String taskId, String backFileName, String FullOrIncrement, String disc, String backFlag) throws Exception;
    public abstract ClientCmdBuilder dbRestore(String mainRestoreFilePathOnClient, List<String> allBackupFiles);
}

@Data
class CommandBean {
    private String callBackUrl;
    private List<List<String>> cmdGroup;
    public CommandBean(String callBackUrl,List<List<String>> cmdGroup)
    {
        this.callBackUrl=callBackUrl;
        this.cmdGroup=cmdGroup;
    }
}